#!/usr/bin/python

import anydbm
import logging
import cPickle
# import UserList
# from patterns import Singleton

class SortedList(list):

  def __init__(self, *args):
    super(SortedList, self).__init__(self, *args)
    self.cmp_func = None

  def setComparator(self, cmp_func):
    self.cmp_func = cmp_func

  def addInOrder(self, item, cmp_func=None):
    """Assumes that the list is sorted, and inserts the new item into that list"""
    if cmp_func is None:
      if self.cmp_func is None:
        cmp_func = cmp
      else:
        cmp_func = self.cmp_func

    # special case the inital addition
    if not self:
      self.append(item)
      return

    b, e = 0, len(self)
    while e>b:
      if cmp_func(item, self[(b+e)/2]) <= 0:
        e=(b+e)/2
      else:
        b=(b+e)/2 + 1
    # assert(self[b] <= item <= self[e])
    if e >= len(self):
      self.append(item)
    else:
      assert(item <= self[e])
      self.insert(e,item)

class _ObjWrapper(object):

  # FIXME(mitch): not sure if this is the right job for __slots__
  attr = ('_objwrap_obj',
          '_objwrap_key',
          '_objwrap_cache',
          '_objwrap_store',
          '_objwrap_touch', 
          '_objwrap_lasttouch',
          '_objwrap_flush',
          '_objwrap_dirty',
          )

  def __init__(self, obj, key, cache, store):
    self._objwrap_obj = obj
    self._objwrap_key = key
    self._objwrap_cache = cache
    self._objwrap_store = store
    self._objwrap_dirty = False
    self._objwrap_touch()

  def _objwrap_touch(self):
    self._objwrap_lasttouch = self._objwrap_cache.counter(incr=True)

  def __getattr__(self, attr):
    if attr in _ObjWrapper.attr:
      logging.debug('wrapped object with key %s got internal attr %s',
                    repr(self._objwrap_key), attr)
      return getattr(self, attr)
    else:
      logging.debug('wrapped object with key %s got wrapped attr %s',
                    repr(self._objwrap_key), attr)
      self._objwrap_touch()
      return getattr(self._objwrap_obj, attr)

  def __setattr__(self, attr, value):
    if attr in _ObjWrapper.attr:
      setattr(self, attr, value)
    else:
      self._objwrap_touch()
      if value != getattr(self._objwrap_obj, attr):
        logging.debug('wrapped obj setting attribute %s to %s '
                      'for key %s, marked dirty',
                      attr, value, repr(self._objwrap_key))
        setattr(self._objwrap_obj, attr, value)
        self._objwrap_dirty=True
      else:
        logging.debug('wrapped obj setting attribute %s to '
                      'same value for key %s', attr, repr(self._objwrap_key))

  def __del__(self):
    self._objwrap_flush();

  def _objwrap_flush(self):
    if self._objwrap_dirty:
      logging.info('wrapped object with key %s is dirty and being written',
                   repr(self._objwrap_key))
      self._objwrap_store[self._objwrap_key] = self._objwrap_obj
      self._objwrap_dirty=False
    else:
      logging.debug('wrapped object with key %s not dirty, not written',
                    repr(self._objwrap_key))
  


class ObjCache(object):
  def __init__(self, store, upper=500000, lower=400000):
    self.upper = upper
    self.lower = lower
    self.store = store
    self._counter = 0
    self._lru = {}
    # I can't remember what these are for..
    # self._cache = {}
    # self._refcount = {}

  def register(self, key, obj, db):
    key = hash(obj)
    if key in self._lru:
      logging.warn('Attempted to register key %s when already in cache', key)
    else:
      self._lru[key] = _ObjWrapper(obj, key, self, db)

    if len(self._lru) > self.upper:
      self.purge()
  
  def purge(self):
    deathrow = SortedList()
    purgecount = self.upper - self.lower 


  def counter(self, incr=False):
    if incr:
      self._counter += 1
    return self._counter

class Objdbm(object):

  def __init__(self, db):
    self.db = db

  def close(self):
    self.db.close()

  def __getitem__(self, key):
    return cPickle.loads(self.db[repr(key)])

  def __setitem__(self, key, value):
    self.db[repr(key)] = cPickle.dumps(value)

  def __delitem__(self, key):
    del self.db[repr(key)]

  def __contains__(self, key):
    return repr(key) in self.db

  def clear(self):
    self.db.clear()

  def __len__(self):
    return len(self.db)

  def __iter__(self):
    return self.iterkeys()

  def iterkeys(self):
    # return [eval(k) for k in self.db.iterkeys()]
    return self.keys()

  def values(self):
    return [cPickle.loads(value) for value in self.db.values()]

  def keys(self):
    return [eval(key) for key in self.db.keys()]

  def items(self):
    return [(eval(key), cPickle.loads(value)) for key, value in self.db.items()]

  def sync(self):
    self.db.sync()

def open(*vargs, **kwargs):
  return Objdbm(anydbm.open(*vargs, **kwargs))
